/*
*  challenges.c - Handle MSN Challenges
*
*  This program is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU Library General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include <glib.h>
#include <string.h>

#include "crypto.h"
#include "challenges.h"

/* This algorithm is a slightly modified version of that found in
/* Pidgin 2.5.2 msnutils.c: MSN_handle_chl(char *input, char *output)
/* I did NOT write it! All credit goes to original authors 

/* This algorithm references 
 http://msnpiki.msnfanatic.com/index.php/MSNP11:Challenges
 and
 http://tmsnc.sourceforge.net/chl.c
 I am not the author of this algorithm and I claim no copyright.
 */

#define BUFSIZE 256
   
   /* TODO: fix THIS algorithm */
char *
msn_process_challenge2 (char *challstr)
{
	const char *productKey = MSN_PRODUCT_KEY;
	const char *productID  = MSN_PRODUCT_ID;
	const char hexChars[]  = "0123456789abcdef";
	char buf[BUFSIZE];
	unsigned char *hashstr = NULL;
	unsigned char *newHash = NULL;
	unsigned int *md5Parts = NULL;
	unsigned int *chlStringParts = NULL;
	unsigned int newHashParts[5];
	char final_hash[32];

	long long nHigh = 0, nLow = 0;

	int len;
	int i;

	hashstr = md5_hash_str ("%s%s", challstr, productKey, NULL);
	if (hashstr == NULL)
		return NULL;

	/* Split it into four integers */
	md5Parts = (unsigned int *)hashstr;
	for (i = 0; i < 4; i++) {
		/* adjust endianess */
		md5Parts[i] = GUINT_TO_LE(md5Parts[i]);

		/* & each integer with 0x7FFFFFFF          */
		/* and save one unmodified array for later */
		newHashParts[i] = md5Parts[i];
		md5Parts[i] &= 0x7FFFFFFF;
	}

	/* make a new string and pad with '0' to length that's a multiple of 8 */
	snprintf(buf, BUFSIZE - 5, "%s%s", challstr, productID);
	len = strlen(buf);
	if ((len % 8) != 0) {
		int fix = 8 - (len % 8);
		memset(&buf[len], '0', fix);
		buf[len + fix] = '\0';
		len += fix;
	}

	/* split into integers */
	chlStringParts = (unsigned int *)buf;

	/* this is magic */
	for (i = 0; i < (strlen(buf) / 4); i += 2) {
		long long temp;

		chlStringParts[i] = GUINT_TO_LE(chlStringParts[i]);
		chlStringParts[i + 1] = GUINT_TO_LE(chlStringParts[i + 1]);

		temp = (0x0E79A9C1 * (long long)chlStringParts[i]) % 0x7FFFFFFF;
		temp = (md5Parts[0] * (temp + nLow) + md5Parts[1]) % 0x7FFFFFFF;
		nHigh += temp;

		temp = ((long long)chlStringParts[i + 1] + temp) % 0x7FFFFFFF;
		nLow = (md5Parts[2] * temp + md5Parts[3]) % 0x7FFFFFFF;
		nHigh += nLow;
	}
	nLow = (nLow + md5Parts[1]) % 0x7FFFFFFF;
	nHigh = (nHigh + md5Parts[3]) % 0x7FFFFFFF;

	newHashParts[0] ^= nLow;
	newHashParts[1] ^= nHigh;
	newHashParts[2] ^= nLow;
	newHashParts[3] ^= nHigh;

	/* adjust endianness */
	for(i = 0; i < 4; i++)
		newHashParts[i] = GUINT_TO_LE(newHashParts[i]);

	/* make a string of the parts */
	newHash = (unsigned char *)newHashParts;

	/* convert to hexadecimal */
	for (i = 0; i < 16; i++)
	{
		final_hash[i * 2] = hexChars[(newHash[i] >> 4) & 0xF];
		final_hash[(i * 2) + 1] = hexChars[newHash[i] & 0xF];
	}

	final_hash[32] = '\0';
	
	return g_strdup (final_hash);
}

int isBigEndian(void)
{
  short int word = 0x0100;
  char *byte = (char *)&word;
  return(byte[0]);
}

unsigned int swapInt(unsigned int dw)
{
  unsigned int tmp;
  tmp =  (dw & 0x000000FF);
  tmp = ((dw & 0x0000FF00) >> 0x08) | (tmp << 0x08);
  tmp = ((dw & 0x00FF0000) >> 0x10) | (tmp << 0x08);
  tmp = ((dw & 0xFF000000) >> 0x18) | (tmp << 0x08);
  return(tmp);
}

char *msn_process_challenge(char *input)
{
  char *productKey = "CFHUR$52U_{VIX5T",
       *productID  = "PROD0101{0RM?UBW",
       *hexChars   = "0123456789abcdef",
       buf[BUFSIZE];
  unsigned char md5Hash[16], *newHash;
  unsigned int *md5Parts, *chlStringParts, newHashParts[5];
  
  long long nHigh=0, nLow=0;
  
  int i, bigEndian;
char output[256];

  /* Determine our endianess */
  bigEndian = isBigEndian();

  /* Create the MD5 hash */
  snprintf(buf, BUFSIZE-1, "%s%s", input, productKey);
  MD5((unsigned char *)buf, strlen(buf), md5Hash);

  /* Split it into four integers */
  md5Parts = (unsigned int *)md5Hash;
  for(i=0; i<4; i++)
  {  
    /* check for endianess */
    if(bigEndian)
      md5Parts[i] = swapInt(md5Parts[i]);
    
    /* & each integer with 0x7FFFFFFF          */
    /* and save one unmodified array for later */
    newHashParts[i] = md5Parts[i];
    md5Parts[i] &= 0x7FFFFFFF;
  }
  
  /* make a new string and pad with '0' */
  snprintf(buf, BUFSIZE-5, "%s%s", input, productID);
  i = strlen(buf);
  memset(&buf[i], '0', 8 - (i % 8));
  buf[i + (8 - (i % 8))]='\0';
  
  /* split into integers */
  chlStringParts = (unsigned int *)buf;
  
  /* this is magic */
  for (i=0; i<(strlen(buf)/4)-1; i+=2)
  {
    long long temp;

    if(bigEndian)
    {
      chlStringParts[i]   = swapInt(chlStringParts[i]);
      chlStringParts[i+1] = swapInt(chlStringParts[i+1]);
    }

    temp=(md5Parts[0] * (((0x0E79A9C1 * (long long)chlStringParts[i]) % 0x7FFFFFFF)+nHigh) + md5Parts[1])%0x7FFFFFFF;
    nHigh=(md5Parts[2] * (((long long)chlStringParts[i+1]+temp) % 0x7FFFFFFF) + md5Parts[3]) % 0x7FFFFFFF;
    nLow=nLow + nHigh + temp;
  }
  nHigh=(nHigh+md5Parts[1]) % 0x7FFFFFFF;
  nLow=(nLow+md5Parts[3]) % 0x7FFFFFFF;
  
  newHashParts[0]^=nHigh;
  newHashParts[1]^=nLow;
  newHashParts[2]^=nHigh;
  newHashParts[3]^=nLow;
  
  /* swap more bytes if big endian */
  for(i=0; i<4 && bigEndian; i++)
    newHashParts[i] = swapInt(newHashParts[i]); 
  
  /* make a string of the parts */
  newHash = (unsigned char *)newHashParts;
  
  /* convert to hexadecimal */
  for (i=0; i<16; i++)
  {
    output[i*2]=hexChars[(newHash[i]>>4)&0xF];
    output[(i*2)+1]=hexChars[newHash[i]&0xF];
  }
  
  output[32]='\0';
  return g_strdup (output);
}

